//
//  app.c
//
//  Ported by Alexander Polyakov on 10/10/24
//  from original z-way-test project
//  Created by Alex Skalozub on 1/6/12.
//
//  Copyright (c) 2012 Z-Wave.Me
//
//

#include <stdio.h>
#include <errno.h>

#include "ZWayLib.h"
#include "ZLogging.h"
#include "ZWayCliPrivate.h"
#include "ZWayCliPrintPrivate.h"
#include "ZWayEsp32WifiPrivate.h"
#include "ZWayEsp32WebserverPrivate.h"
#include "ZWayCliPeripheryPrivate.h"
#include "ZDefsPrivate.h"
#include "ZDefsPrivate.h"
#include "ZSerializeDownloadPrivate.h"
#include "ZFileOperationsPrivate.h"
#include "ZSerialIO.h"

#include "esp_system.h"
#include "esp_log.h"
#include "esp_vfs_dev.h"
#include "nvs_flash.h"
#include "driver/gpio.h"
#include "esp_netif_sntp.h"
#include "mdns.h"
#include "esp_mac.h"

#ifdef ZWAY_ON_ETHERNET
#include "ZWayEsp32EthernetPrivate.h"
#endif

#ifndef ZWAY_ESP32_DEFAULT_ZWAY_LOG_LEVEL
#define ZWAY_ESP32_DEFAULT_ZWAY_LOG_LEVEL Debug
#endif


#ifdef ZWAY_ON_ETHERNET
static _ZWayEsp32EthCtx_t *ctx_eth = NULL;
#else
static _ZwayEsp32WifiCtx_t *ctx_wifi = NULL;
#endif
static _ZwayEsp32WebServerCtx_t ctx_web_server;

#if defined(XIAO_SHIELD_TEST_EXT) 
static ZWError __init_xiao_test(void);
#endif

static ZWCSTR __add_help(void)
{
    return (
        "w [SSID PASSWD] Setups WiFi credentials.\n"
#if defined(ZWAY_ESP32_RESCUE_BTN)
        "b Check buttons state\n"
#endif
#if defined(XIAO_SHIELD_TEST_EXT) 
        "T Starts Xiao board hardware test\n"
#endif 
        "I Prints IP-address information.\n");
}

#if defined(XIAO_SHIELD_TEST_EXT) 
static void __add_parse_T(void)
{
    (void)__init_xiao_test();
    int i;
    for (i = 0; i < 20; i++)
    {
        gpio_set_level(XIOA_SHIELD_QWIIC_SDA, i & 0x01);
        gpio_set_level(XIOA_SHIELD_QWIIC_SCL, i & 0x02);
        usleep(100000UL);
    } 
    gpio_set_level(XIOA_SHIELD_QWIIC_SDA, 0);
    gpio_set_level(XIOA_SHIELD_QWIIC_SCL, 0);   
}
#endif

static void __add_parse_w(ZWCSTR cmd_buffer)
{
    int nconv;
    _ZwayEsp32WifiCredentials_t cred;
    ZWCHAR cmd;
    
    nconv = sscanf(cmd_buffer, "%c %s %s", &cmd, cred.info.ssid, cred.info.psw);
    if (nconv == 1)
    {
        if (_zway_esp32_load_wifi_credentials(ZWAY_ESP32_WIFI_CREDENTIAL_FILENAME, &cred) == NoError)
        {
            (void)printf("\nCurrent WIFI credentials: %s %s\n\n", cred.info.ssid, cred.info.psw);
        }
        else
        {
            (void)printf("\nThere are no WIFI credentials!\n\n");
        }
    }
    else if (nconv == 3)
    {
        if (_zway_esp32_save_wifi_credentials(ZWAY_ESP32_WIFI_CREDENTIAL_FILENAME, &cred) == NoError)
        {
            (void)printf("\nSave WIFI credentials DONE!\n\n");
        }
        else
        {
            (void)printf("\nSave WIFI credentials FAIL!\n\n");
        }
    }
    else
    {
        (void)printf("Syntax error!\n");
    }
}

static void __add_parse_I(void)
{
    ZwayEsp32Hostname_t hostname;
    ZWDWORD ip4;
    int i;
    
    (void)printf("Device IP address: ");
    #ifdef ZWAY_ON_ETHERNET
    if (ctx_eth != NULL && (ip4 = _zway_esp32_get_eth_ip4(ctx_eth)) != 0)
    #else
    if (ctx_wifi != NULL && (ip4 = _zway_esp32_get_wifi_ip4(ctx_wifi)) != 0)
    #endif
    {
        for (i = 0; i < 4; i++, ip4 >>= 8)
        {
            (void)printf("%d", ip4 & 0xFF);
            if (i != 3)
            {
                (void)printf(".");
            }
        }
    }
    else
    {
        (void)printf("Unknown");
    }
    (void)printf("\n");
    if (_zway_esp32_get_hostname(&hostname) != NoError)
    {
        (void)printf("Device hostname: Unknown\n");
        return;
    }
    (void)printf("Device hostname: %s\nBrowser: http://%s.local:%d\n", hostname.data, hostname.data, ZWAY_ESP_WEB_SERVER_PORT);
    #ifndef ZWAY_ON_ETHERNET
    (void)printf("SoftAP SSID: %s\nSoftAP password: %s\n", hostname.data, ZWAY_ESP32_DEFAULT_AP_WIFI_PASS);
    #endif
}

static ZWBOOL __add_parse(ZWCSTR cmd_buffer)
{
    ZWCHAR cmd;
    int nconv;
    
    nconv = sscanf(cmd_buffer, "%c", &cmd);
    if (nconv <= 0)
    {
        return FALSE;
    }

    switch (cmd)
    {
#if defined(ZWAY_ESP32_RESCUE_BTN)
        case 'b':
            printf("BTN state:%d\n", gpio_get_level(ZWAY_ESP32_RESCUE_BTN));
            break;
#endif
        case 'w':
            __add_parse_w(cmd_buffer);
            break;
        #if defined(XIAO_SHIELD_TEST_EXT) 
        case 'T':
            __add_parse_T();
            break;
        #endif
        case 'I':
            __add_parse_I();
            break;
        default:
            return FALSE;
    }
    return TRUE;
}

static ZWError __init_tcp_ip_stack(void)
{
    if (esp_netif_init() != ESP_OK)
    {
        return InternalError;
    }
    if (esp_event_loop_create_default() != ESP_OK)
    {
        return InternalError;
    }
    return NoError;
}
#if defined(XIAO_SHIELD_TEST_EXT)
static void IRAM_ATTR __xiao_button_isr_handler(void *arg)
{
    static uint8_t __xiao_test_pins[2] = {5, 6};
    static uint8_t __xiao_test_levels[2];
    int index = (int)arg;
    __xiao_test_levels[index] = 1 - __xiao_test_levels[index];
    gpio_set_level(__xiao_test_pins[index], __xiao_test_levels[index]);
}
static ZWError __init_xiao_test(void)
{
    // The second button 
    #ifdef XIOA_SHIELD_BTN2
    if (gpio_reset_pin(XIOA_SHIELD_BTN2) != ESP_OK)
    {
        return InternalError;
    }
    if (gpio_set_direction(XIOA_SHIELD_BTN2, GPIO_MODE_INPUT) != ESP_OK)
    {
        return InternalError;
    }
    if (gpio_set_pull_mode(XIOA_SHIELD_BTN2, GPIO_PULLUP_ONLY) != ESP_OK)
    {
        return InternalError;
    }
    #endif
    #if defined(XIOA_SHIELD_QWIIC_SDA) && defined(XIOA_SHIELD_QWIIC_SCL)
    // QWIIC connnector for tests
    if (gpio_reset_pin(XIOA_SHIELD_QWIIC_SDA) != ESP_OK)
    {
        return InternalError;
    }
    if (gpio_set_direction(XIOA_SHIELD_QWIIC_SDA, GPIO_MODE_OUTPUT) != ESP_OK)
    {
        return InternalError;
    }
    gpio_set_level(XIOA_SHIELD_QWIIC_SDA, 0);
    if (gpio_reset_pin(XIOA_SHIELD_QWIIC_SCL) != ESP_OK)
    {
        return InternalError;
    }
    if (gpio_set_direction(XIOA_SHIELD_QWIIC_SCL, GPIO_MODE_OUTPUT) != ESP_OK)
    {
        return InternalError;
    }
    gpio_set_level(XIOA_SHIELD_QWIIC_SCL, 0);
    #endif // defined(XIOA_SHIELD_QWIIC_SDA) && defined(XIOA_SHIELD_QWIIC_SCL)
    #ifdef XIOA_SHIELD_BTN2
    gpio_set_intr_type(XIOA_SHIELD_BTN2, GPIO_INTR_POSEDGE);
    gpio_install_isr_service(XIOA_SHIELD_BTN2);
    gpio_isr_handler_add(XIOA_SHIELD_BTN2, __xiao_button_isr_handler, (void *)0);
    #endif
    #ifdef ZWAY_ESP32_RESCUE_BTN
    gpio_set_intr_type(ZWAY_ESP32_RESCUE_BTN, GPIO_INTR_POSEDGE);
    gpio_install_isr_service(ZWAY_ESP32_RESCUE_BTN);
    gpio_isr_handler_add(ZWAY_ESP32_RESCUE_BTN, __xiao_button_isr_handler, (void *)1);
    #endif
    return NoError;
}
#endif // XIAO_SHIELD_TEST_EXT

static ZWError __init_gpio(void)
{
#if defined(ZWAY_ESP32_RESCUE_BTN)
    // Set Rescue Button pin mode
    if (gpio_reset_pin(ZWAY_ESP32_RESCUE_BTN) != ESP_OK)
    {
        return InternalError;
    }
    if (gpio_set_direction(ZWAY_ESP32_RESCUE_BTN, GPIO_MODE_INPUT) != ESP_OK)
    {
        return InternalError;
    }
    if (gpio_set_pull_mode(ZWAY_ESP32_RESCUE_BTN, GPIO_PULLUP_ONLY) != ESP_OK)
    {
        return InternalError;
    }
#endif
#if defined(ZWAY_ESP32_RST_PIN)
    if (gpio_reset_pin(ZWAY_ESP32_RST_PIN) != ESP_OK)
    {
        return InternalError;
    }
    if (gpio_set_direction(ZWAY_ESP32_RST_PIN, GPIO_MODE_OUTPUT) != ESP_OK)
    {
        return InternalError;
    }
    gpio_set_level(ZWAY_ESP32_RST_PIN, 1);
    usleep(5000UL);
    gpio_set_level(ZWAY_ESP32_RST_PIN, 0);
    usleep(50000UL);
    gpio_set_level(ZWAY_ESP32_RST_PIN, 1);
#endif

    return NoError;
}

static ZWError __init_nvs(void)
{
    esp_err_t vret;

    vret = nvs_flash_init();
    if (vret == ESP_ERR_NVS_NO_FREE_PAGES || vret == ESP_ERR_NVS_NEW_VERSION_FOUND)
    {
        if (nvs_flash_erase() != ESP_OK)
        {
            return InternalError;
        }
        if (nvs_flash_init() != ESP_OK)
        {
            return InternalError;
        }
    }
    return NoError;
}

static ZWError __init_mdns_service(void)
{
    ZwayEsp32Hostname_t hostname;
    ZWError err;

    if (mdns_init() != ESP_OK)
    {
        return InternalError;
    }

    if ((err = _zway_esp32_get_hostname(&hostname)) != NoError)
    {
        return err;
    }
    // set hostname
    if (mdns_hostname_set(hostname.data) != ESP_OK)
    {
        return InternalError;
    }
    // set default instance
    if (mdns_instance_name_set("Z-Way controller based on ESP32 chip") != ESP_OK)
    {
        return InternalError;
    }
    // add our services
    if (mdns_service_add(NULL, "_http", "_tcp", ZWAY_ESP_WEB_SERVER_PORT, NULL, 0) != ESP_OK)
    {
        return InternalError;
    }
    // NOTE: services must be added before their properties can be set
    // use custom instance for the web server
    if (mdns_service_instance_name_set("_http", "_tcp", hostname.data) != ESP_OK)
    {
        return InternalError;
    }
    return NoError;
}
#ifdef ZWAY_ON_ETHERNET
static ZWError __init_ethernet(const ZWay zway)
{
    return _zway_esp32_ethernet_connect(&ctx_eth, zway_get_logger(zway));
}
#else
static ZWError __init_wifi(ZWay zway)
{
    ZWBOOL force_ap;

#if defined(ZWAY_ESP32_RESCUE_BTN)
    // if button is down go to rescue mode:
    if (gpio_get_level(ZWAY_ESP32_RESCUE_BTN) == 0)
    {
        force_ap = TRUE;
    }
    else
    {
        force_ap = FALSE;
    }
#else
    force_ap = FALSE;
#endif
    ZWError err = _zway_esp32_wifi_connect(&ctx_wifi, zway_get_logger(zway), zway_get_name(zway), force_ap);
    // If there is no wifi credentials we have an error and switch to softap mode
    if (err == NoError)
    {
        return NoError;
    }
    zway_log(zway, Warning, "Unable to connect to WiFi - switching to soft app mode.");
    return _zway_esp32_wifi_connect(&ctx_wifi, zway_get_logger(zway), zway_get_name(zway), TRUE);
}
#endif 
static ZWError __init_ntp(ZWay zway)
{
    static const esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG("pool.ntp.org");

    if (esp_netif_sntp_init(&config) != ESP_OK)
    {
        return InternalError;
    }
    zway_log(zway, Information, "Wait Sync time using NTP...");
    if (esp_netif_sntp_sync_wait(pdMS_TO_TICKS(10000)) != ESP_OK)
    {
        return BadData;
    }
    return NoError;
}

// We don't close or cancel anything because we'll reboot to esp anyway
static ZWError __app_proc(ZWLog logger)
{
    ZWay zway;
    ZWError err;

    if ((err = _zway_cli_periphery_fs_init()) != NoError)
    {
        ZWAY_ESP32_LOG_ERR_EXT(logger, "esp", Critical, "Failed to init file system", err);
        return err;
    }
    if ((err = __init_gpio()) != NoError)
    {
        ZWAY_ESP32_LOG_ERR_EXT(logger, "esp", Critical, "Failed to init gpio", err);
        return err;
    }
    zway = NULL;
    if ((err = zway_init(&zway, zio_port_settings_generate(ZWAY_DEFAULT_COM_PORT_FOR_CONTROLER, 1, ZWAY_ESP32_UART1_TX, ZWAY_ESP32_UART1_RX, 768), 0, NULL, NULL, NULL, NULL, logger)) != NoError)
    {
        ZWAY_ESP32_LOG_ERR_EXT(logger, "esp", Critical, "Failed to init ZWay", err);
        return err;
    }
    if ((err = zway_device_add_callback(zway, DeviceAdded | DeviceRemoved | InstanceAdded | InstanceRemoved | CommandAdded | CommandRemoved, _zway_cli_print_d_i_cc_event, NULL)) != NoError)
    {
        ZWAY_ESP32_LOG_ERR(zway, Critical, "Failed to init callback for zway", err);
        return err;
    }
    zlog_write(logger, "esp", Information, "Init NVS...");
    err = __init_nvs();
    if (err != NoError)
    {
        ZWAY_ESP32_LOG_ERR(zway, Critical, "Failed to init nvs", err);
        return err;
    }
    zlog_write(logger, "esp", Information, "Init IP Stack...");
    if ((err = __init_tcp_ip_stack()) != NoError)
    {
        ZWAY_ESP32_LOG_ERR(zway, Critical, "Failed to init TCP/IP stack", err);
        return err;
    }
    zlog_write(logger, "esp", Information, "Init WIFI...");
    #ifdef ZWAY_ON_ETHERNET
    err = __init_ethernet(zway);
    if (err != NoError)
    {
        ZWAY_ESP32_LOG_ERR(zway, Critical, "Failed to init ethernet", err);
        return err;
    }
    #else
    if ((err = __init_wifi(zway)) != NoError)
    {
        ZWAY_ESP32_LOG_ERR(zway, Critical, "Failed to init wifi", err);
        return err;
    }
    #endif
    zlog_write(logger, "esp", Information, "Init NTP..");

    // Sync time using NTP
    if ((err = __init_ntp(zway)) != NoError)
    {
        zway_log_error(zway, Information, "Failed Sync time using NTP", err);
    }

    if ((err = zway_start(zway, _zway_cli_print_zway_terminated, NULL)) != NoError)
    {
        ZWAY_ESP32_LOG_ERR(zway, Critical, "Failed to start ZWay", err);
        return err;
    }
    if ((err = zway_discover(zway)) != NoError)
    {
        ZWAY_ESP32_LOG_ERR(zway, Critical, "Failed to negotiate with Z-Wave stick", err);
        return err;
    }
    
    if ((err = __init_mdns_service()) != NoError)
    {
        ZWAY_ESP32_LOG_ERR(zway, Critical, "Failed to init mdns service", err);
        return err;
    }
    // Start WebServer
    if ((_zway_esp32_web_server_start(&ctx_web_server, zway)) != NoError)
    {
        ZWAY_ESP32_LOG_ERR(zway, Critical, "Failed to init web server", err);
        return err;
    }

    // Starts the CLI interface
    err = _zway_cli_process(zway, __add_help(), __add_parse);
    (void)_zway_esp32_web_server_stop(&ctx_web_server);
    
    (void)zway_stop(zway);
    
    zway_terminate(&zway);

    zlog_close(logger);
    
    return err;
}

static ZWError __app_main(void)
{
    ZWLog logger;
    ZWError err;
    
    if ((logger = zlog_create(stdout, ZWAY_ESP32_DEFAULT_ZWAY_LOG_LEVEL)) == NULL)
    {
        ESP_LOGI("esp", "unable to create log!\n");
        return AccessDenied;
    }
    
    err = __app_proc(logger);
    if (err != NoError)
    {
        zlog_error(logger, "esp", Debug, "Finished working with status", err);
    }
    ESP_LOGI("esp", "__app_proc was exited!\n");
    return err;
}

void app_main(void)
{
    if (__app_main() != NoError)
    {
        ESP_LOGI("esp", "main thread 15 sec for restart");
        sleep(15);
    }
    esp_restart();
}